// Package nginx 操作 nginx, -s reload/stop, -t 等等
package nginx

import (
	"bytes"
	"database/sql"
	"fmt"
	"github.com/MzoroHaHa/dev-tools/backend/app/utils"
	"github.com/gin-gonic/gin"
	"log/slog"
	"os"
	"path"
	"strconv"
	"text/template"
)

func (n *NgController) testConfig(c *gin.Context) {
	id := c.Query("id")
	n.logger.Info("test nginx config", "id", id)
	if len(id) == 0 {
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  "can not get id",
		})
		return
	}
	e := testConfig(id, n.db, n.logger)
	if e != nil {
		handleErr(e, nil, c)
		return
	}
	c.JSON(200, gin.H{
		"code": 1,
	})
}

func (n *NgController) startNginx(c *gin.Context) {
	id := c.Query("id")
	n.logger.Info("start nginx config", "id", id)
	if len(id) == 0 {
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  "can not get id",
		})
		return
	}
	ngInfo, e := getNginxInfo(id, n.db, n.logger)
	if e != nil {
		handleErr(e, nil, c)
		return
	}
	e, o := start(ngInfo.ExecPath, ngInfo.WorkDir)
	if e != nil {
		n.logger.Error("start nginx", "id", id, "work_dir", ngInfo.WorkDir, "error", e, "output", o)
		msg := fmt.Sprintf("start nginx error, id %s, output: %s , error: %w", id, o, e)
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  msg,
		})
		return
	}

	// 测试是否成功
	n.logger.Info("start nginx success", "id", id, "out", o)
	c.JSON(200, gin.H{
		"code": 1,
	})
}

func (n *NgController) stopNginx(c *gin.Context) {
	id := c.Query("id")
	n.logger.Info("stop nginx config", "id", id)
	if len(id) == 0 {
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  "can not get id",
		})
		return
	}
	ngInfo, e := getNginxInfo(id, n.db, n.logger)
	if e != nil {
		handleErr(e, nil, c)
		return
	}

	e, o := stop(ngInfo.ExecPath, ngInfo.WorkDir)
	if e != nil {
		n.logger.Error("stop nginx", "id", id, "work_dir", ngInfo.WorkDir, "error", e, "output", o)
		msg := fmt.Sprintf("stop nginx error, id %s, output: %s ", id, o)
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  msg,
		})
		return
	}

	// 测试是否成功
	n.logger.Info("stop nginx success", "id", id, "out", o)
	c.JSON(200, gin.H{
		"code": 1,
	})

}

func (n *NgController) reloadNginx(c *gin.Context) {
	id := c.Query("id")
	n.logger.Info("reload nginx config", "id", id)
	if len(id) == 0 {
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  "can not get id",
		})
		return
	}
	ngInfo, e := getNginxInfo(id, n.db, n.logger)
	if e != nil {
		handleErr(e, nil, c)
		return
	}

	e, o := reload(ngInfo.ExecPath, ngInfo.WorkDir)
	if e != nil {
		n.logger.Error("reload nginx", "id", id, "work_dir", ngInfo.WorkDir, "error", e, "output", o)
		msg := fmt.Sprintf("reload nginx error, id %s, output: %s ", id, o)
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  msg,
		})
		return
	}

	// 测试是否成功
	n.logger.Info("reload nginx success", "id", id, "out", o)
	c.JSON(200, gin.H{
		"code": 1,
	})

}

type QueryDao interface {
	Query(query string, args ...any) (*sql.Rows, error)
}

func testConfig(id string, db QueryDao, logger *slog.Logger) (e error) {

	ngInfo, e := getNginxInfo(id, db, logger)
	if e != nil {
		return e
	}
	if ngInfo == nil {
		return fmt.Errorf("can not get nginx info, id %s", id)
	}
	workDir := ngInfo.WorkDir
	execPath := ngInfo.ExecPath
	// 查询配置明细
	logger.Info("test nginx config", "id", id, "work_dir", workDir, "exec_path", execPath)
	configDetail, e := getNginxConfigDetail(db, id)
	if e != nil {
		return e
	}
	if configDetail == nil {
		return fmt.Errorf("can not get nginx config detail, id " + id)
	}

	configFilePath := path.Join(workDir, "conf", "nginx.conf")
	configFileBkPath := path.Join(workDir, "conf", "nginx_bk.conf")
	configFileInfo, e := os.Stat(configFilePath)
	if e == nil && configFileInfo != nil && !configFileInfo.IsDir() {
		e = os.Rename(configFilePath, configFileBkPath)
		if e != nil {
			return e
		}
	}
	e = writeConfigFiles(workDir, configDetail, "_stream", "_http")
	if e != nil {
		return e
	}

	e, o := test(execPath, workDir)
	if e != nil {
		logger.Error("test nginx config", "id", id, "work_dir", workDir, "error", e, "output", o)
		return fmt.Errorf("test nginx config error, id %s, output: %s , error: %w", id, o, e)
	}
	logger.Info("test nginx success", "id", id, "out", o)

	// 还原配置文件
	_ = os.Remove(configFilePath)
	configFileBkInfo, e := os.Stat(configFileBkPath)
	if e == nil && configFileBkInfo != nil && !configFileBkInfo.IsDir() {
		_ = os.Rename(configFileBkPath, configFilePath) // 还原不回去就拉倒
	}

	// 删除临时文件
	e = os.RemoveAll(path.Join(workDir, "conf", "_stream"))
	e = os.RemoveAll(path.Join(workDir, "conf", "_http"))
	return nil
}

func writeConfig(id string, db QueryDao, logger *slog.Logger) error {
	info, e := getNginxInfo(id, db, logger)
	if e != nil {
		return e
	}
	if info == nil {
		return fmt.Errorf("can not get nginx info, id " + id)
	}
	detail, e := getNginxConfigDetail(db, id)
	if e != nil {
		return e
	}
	return writeConfigFiles(info.WorkDir, detail, "stream", "http")
}

var mainConfigTmpl *template.Template
var httpServerTmpl *template.Template
var httpUpstreamTmpl *template.Template
var streamServerTmpl *template.Template
var streamUpstreamTmpl *template.Template

func init() {
	mainConfig := `{{range $index, $value := .BasicConfigs}}{{$value.Code}} {{$value.Value}};{{end}}
events {
    worker_connections  1024;
}
stream {
    {{range $index, $value := .Stream.BasicConfigs}}{{$value.Code}} {{$value.Value}};
    {{end}}

	include {{.StreamInclude}}/*.conf;
}

http {
{{range $index, $value := .Http.BasicConfigs}}
    {{$value.Code}} {{$value.Value}};
{{end}}
	include {{.HttpInclude}}/*.conf;
}
`

	_mainConfigTmpl, e := template.New("mainConfig").Parse(mainConfig)
	mainConfigTmpl = _mainConfigTmpl
	if e != nil {
		panic(e)
	}

	httpServerConfig := `server {
    listen {{.Port}};
    server_name {{.Server}};

    {{range $index, $value := .BasicConfigs}}{{$value.Code}} {{$value.Value}};
    {{end}}
{{range $index, $value := .LocationConfigs}}
    location {{$value.Modifier}} {{$value.Uri}} {
		{{range $in, $v := $value.BasicConfigs}}
        {{$v.Code}} {{$v.Value}};
        {{end}}
	}{{end}}
}`
	_httpServerTmpl, e := template.New("httpServer").Parse(httpServerConfig)
	if e != nil {
		panic(e)
	}
	httpServerTmpl = _httpServerTmpl

	httpUpstreamConfig := `upstream {{.Name}} {
    {{range $index, $value := .BasicConfigs}}{{$value.Code}} {{$value.Value}};
    {{end}}
}
`
	_httpUpstreamTmpl, e := template.New("httpUpstream").Parse(httpUpstreamConfig)
	if e != nil {
		panic(e)
	}
	httpUpstreamTmpl = _httpUpstreamTmpl

	streamServerConfig := `server {
    listen {{.Listen}};

    {{range $index, $value := .BasicConfigs}}{{$value.Code}} {{$value.Value}};
    {{end}}
}
`
	_streamServerTmpl, e := template.New("streamServer").Parse(streamServerConfig)
	if e != nil {
		panic(e)
	}
	streamServerTmpl = _streamServerTmpl

	streamUpstreamConfig := `upstream {{.Name}} {
    {{range $index, $value := .BasicConfigs}}{{$value.Code}} {{$value.Value}};
    {{end}}
}
`

	_streamUpstreamTmpl, e := template.New("streamUpstream").Parse(streamUpstreamConfig)
	if e != nil {
		panic(e)
	}
	streamUpstreamTmpl = _streamUpstreamTmpl
}

func checkOrMakeDir(dirPath string) (os.FileInfo, error) {
	confDirInfo, e := os.Stat(dirPath)
	if e != nil {
		if os.IsNotExist(e) {

			e = os.MkdirAll(dirPath, 0750)
			if e != nil {
				return confDirInfo, e
			}
			confDirInfo, e = os.Stat(dirPath)
		} else {
			return confDirInfo, e
		}
	}

	if confDirInfo == nil {
		return nil, fmt.Errorf("can not find work dir")
	}

	if !confDirInfo.IsDir() {
		return nil, fmt.Errorf("work dir is not dir")
	}
	return confDirInfo, nil
}
func writeConfigFiles(workDir string, configDetail *Config, streamDirName, httpDirName string) error {

	confDir := path.Join(workDir, "conf")

	_, e := checkOrMakeDir(confDir)
	if e != nil {
		return e
	}
	tmpDir := path.Join(workDir, "temp")

	_, e = checkOrMakeDir(tmpDir)
	if e != nil {
		return e
	}
	clientBodyTempDir := path.Join(tmpDir, "client_body_temp")
	_, e = checkOrMakeDir(clientBodyTempDir)
	if e != nil {
		return e
	}
	// 写入 nginx.conf
	var w bytes.Buffer
	e = mainConfigTmpl.Execute(&w, TestConfig{
		Config:        *configDetail,
		StreamInclude: streamDirName,
		HttpInclude:   httpDirName,
	})

	if e != nil {
		return e
	}
	e = os.WriteFile(path.Join(confDir, "nginx.conf"), w.Bytes(), os.ModePerm)
	if e != nil {
		return e
	}
	// http include 配置
	httpDir := path.Join(confDir, httpDirName)
	e = os.MkdirAll(httpDir, os.ModePerm)
	if e != nil {
		return e
	}
	if len(configDetail.Http.ServerConfig) != 0 {

		for _, conf := range configDetail.Http.ServerConfig {
			fileName := "server_" + utils.FormatToFileName(conf.Server) + "_" + strconv.Itoa(conf.Port) + ".conf"
			filePath := path.Join(httpDir, fileName)
			var s bytes.Buffer
			e = httpServerTmpl.Execute(&s, conf)
			if e != nil {
				return e
			}
			e = os.WriteFile(filePath, s.Bytes(), os.ModePerm)
			if e != nil {
				return e
			}
		}
	}
	if len(configDetail.Http.UpstreamConfig) != 0 {

		for _, conf := range configDetail.Http.UpstreamConfig {
			fileName := "upstream_" + utils.FormatToFileName(conf.Name) + ".conf"
			filePath := path.Join(httpDir, fileName)
			var s bytes.Buffer
			e = httpUpstreamTmpl.Execute(&s, conf)
			if e != nil {
				return e
			}
			e = os.WriteFile(filePath, s.Bytes(), os.ModePerm)
			if e != nil {
				return e
			}
		}
	}
	// stream include 配置
	streamDir := path.Join(confDir, streamDirName)
	e = os.MkdirAll(streamDir, os.ModePerm)
	if e != nil {
		return e
	}
	if len(configDetail.Stream.ServerConfig) != 0 {
		for _, conf := range configDetail.Stream.ServerConfig {
			fileName := "server_" + utils.FormatToFileName(conf.Listen) + ".conf"
			filePath := path.Join(streamDir, fileName)
			var s bytes.Buffer
			e = streamServerTmpl.Execute(&s, conf)
			if e != nil {
				return e
			}
			e = os.WriteFile(filePath, s.Bytes(), os.ModePerm)
			if e != nil {
				return e
			}
		}
	}

	if len(configDetail.Stream.UpstreamConfig) != 0 {
		for _, conf := range configDetail.Stream.UpstreamConfig {
			fileName := "stream_" + utils.FormatToFileName(conf.Name) + ".conf"
			filePath := path.Join(streamDir, fileName)
			var s bytes.Buffer
			e = streamUpstreamTmpl.Execute(&s, conf)
			if e != nil {
				return e
			}
			e = os.WriteFile(filePath, s.Bytes(), os.ModePerm)
			if e != nil {
				return e
			}
		}
	}
	return nil
}
